Exceptions

In an ideal situation, our program runs smoothly without any errors. However it is not always the case. Errors may be due to developer's fault or programmer's mistake or of computer. Source of some errors might be hard to undertsand. However it is the task of Good Programmer to handle all kinds of errors that might occur in his program. If some error condition escapes from the developer and user catches it, It is a bug in the program. Developers must update the programs periodically to fix the bugs in the software. You may remember that recent ransomware attack which caused the loss of enormous amount of data, was due to a bug in Microsoft Windows.

Facing a first exception

Let's write a lambda to divide 2 numbers


In [1]:
div = lambda x,y : x/y

In [2]:
div(8,2)


Out[2]:
4.0

In [3]:
div(0/0)


---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-3-d742f81f0a4a> in <module>()
----> 1 div(0/0)

ZeroDivisionError: division by zero

Oh No!... It was a error. Let's handle it.

try-except-finally

try-except-finally provides an easy way to handle errors that can arise during program execution. It works similar to try-catch-finally blocks in Java and C#

Syntax:

try:
        <statement 1>
        <statement 2>
        ...
        <statement n>
    except (Exception List):   # Refer note
        <statement 1>
        <statement 2>
        ...
        <statement n>
    finally:
        <cleanup 1>
        <cleanup 2>
        ...
        <cleanup n>
**Note:** - `finally` block is optional - If Exception List is empty all exceptions are handled by `except` block - If catching a single exception, it can be referred with its name. ```python except RangeError as e: ``` - Base Exception classes must be captured at last, if catching exceptions in hierarchy

div with exception handling


In [5]:
def div_good(x,y):
    try:
        return x/y
    except ZeroDivisionError:
        print("Division by zero")

In [6]:
div_good(8,2)


Out[6]:
4.0

In [7]:
div_good(0,0)


Division by zero

Note how the exception was handled

Cleaning the things up

In this version of div, we will return a NaN if a ZeroDivisionError occures. 'NaN' is Not a Number. 'Inf' refers infinity


In [4]:
def div_clean(x,y):
    try:
        value = x/y
    except ZeroDivisionError:
        value = float('NaN')
    return value

In [2]:
div_clean(4,3)


Out[2]:
1.3333333333333333

In [3]:
div_clean(8,0)


Out[3]:
nan

Raising Exceptions

The raise statement allows the programmer to force a specified exception to occur. For example:


In [5]:
raise NameError('HiThere')


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-93385ba972b1> in <module>()
----> 1 raise NameError('HiThere')

NameError: HiThere

The sole argument to raise indicates the exception to be raised. This must be either an exception instance or an exception class (a class that derives from Exception). If an exception class is passed, it will be implicitly instantiated by calling its constructor with no arguments:


In [6]:
raise ValueError  # shorthand for 'raise ValueError()'


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-f4e87a14b34e> in <module>()
----> 1 raise ValueError  # shorthand for 'raise ValueError()'

ValueError: 

If you need to determine whether an exception was raised but don’t intend to handle it, a simpler form of the raise statement allows you to re-raise the exception:


In [7]:
try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise


An exception flew by!
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-7-3f47609917d7> in <module>()
      1 try:
----> 2     raise NameError('HiThere')
      3 except NameError:
      4     print('An exception flew by!')
      5     raise

NameError: HiThere

User-defined Exceptions

Programs may name their own exceptions by creating a new exception class. Exceptions should typically be derived from the Exception class, either directly or indirectly.

Exception classes can be defined which do anything any other class can do, but are usually kept simple, often only offering a number of attributes that allow information about the error to be extracted by handlers for the exception. When creating a module that can raise several distinct errors, a common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions:


In [1]:
class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

Most exceptions are defined with names that end in Error, similar to the naming of the standard exceptions.